export function patch(el, vnode) {
  // 没有el，说明是个组件，把vnode变成真实dom返回
  if (!el) {
    return createElm(vnode)
  }

  let isRealElement = el.nodeType
  // 初始渲染，先删旧的，再加新的
  if (isRealElement) {
    let parentElm = el.parentNode
    let newElement = createElm(vnode)
    parentElm.insertBefore(newElement, el.nextSibling)
    parentElm.removeChild(el)

    // 必须要返回，不然下一次找不到 el.parentNode !!!
    return newElement
  } else {
    // 走diff算法，更新视图
    patchVnode(el, vnode)
  }
}

// 创建元素
function createElm(vnode) {
  let { tag, data, children, text } = vnode
  if (typeof tag === "string") {
    // 创建真实元素之前看这个是否是个组件
    if (createComponent(vnode)) {
      return vnode.componentInstance.$el // 这个el就是组件的渲染结果
    }

    vnode.el = document.createElement(tag)
    patchProps(vnode.el, {}, data)
    children.forEach(child => {
      vnode.el.appendChild(createElm(child))
    })
  } else {
    vnode.el = document.createTextNode(text)
  }
  return vnode.el
}

// 创建组件元素
function createComponent(vnode) {
  let i = vnode.data
  // i 就是 data.hook.init
  if ((i = i.hook) && (i = i.init)) {
    i(vnode)
  }
  if (vnode.componentInstance) {
    return true
  }
  return false
}

// 更新属性
function patchProps(el, oldProps = {}, props = {}) {
  let oldStyles = oldProps.style || {}
  let newStyles = props.style || {}

  // 对比样式，如果老的属性有，新的属性没有，删除老的
  for (let key in oldStyles) {
    if (!newStyles[key]) {
      el.style[key] = ""
    }
  }

  // 对比其他属性, a=1 b=2 这种 ，如果没有，删除
  for (let key in oldProps) {
    if (!props[key]) {
      el.removeAttribute(key)
    }
  }

  // 用新的覆盖老的
  for (let key in props) {
    if (key === "style") {
      for (let n in props.style) {
        el.style[n] = props.style[n]
      }
    } else {
      el.setAttribute(key, props[key])
    }
  }
}

// 对比vnode是否相同
function patchVnode(oldVnode, vnode) {
  let el
  // 如果tag和key不同，则不是同一个节点，直接替换
  if (!isSameVnode(oldVnode, vnode)) {
    // 如果不同，创建新的节点
    el = createElm(vnode)
    oldVnode.el.parentNode.replaceChild(el, oldVnode.el)
    return el
  }

  // 如果是文本，则替换文本，复用旧的 el
  el = vnode.el = oldVnode.el
  if (!oldVnode.tag) {
    if (oldVnode.text !== vnode.text) {
      el.textContent = vnode.text
    }
  }

  // 如果是标签，则比对属性是否相同
  patchProps(el, oldVnode.data, vnode.data)

  // 比较两个节点的children
  let oldChildren = oldVnode.children || []
  let newChildren = vnode.children || []

  if (oldChildren.length > 0 && newChildren.length > 0) {
    // 老的有，新的也有，走完整的diff
    updateChildren(el, oldChildren, newChildren)
  } else if (newChildren.length > 0) {
    // 老的没有，新的有
    mountChildren(el, newChildren)
  } else if (oldChildren.length > 0) {
    // 新的没有，老的有，删除
    el.innerHTML = ""
  }

  return el
}

// 比对两个节点是否相同
function isSameVnode(oldVnode, vnode) {
  return oldVnode.tag === vnode.tag && oldVnode.key === vnode.key
}

// 添加新的children
function mountChildren(el, children) {
  for (let i = 0; i < children.length; i++) {
    let child = children[i]
    let newEle = createElm(child) // 创建真实的dom
    el.appendChild(newEle)
  }
}

/**
 * 完整的 diff 算法，双指针的方式，比较两个节点
 */
function updateChildren(el, oldChildren, newChildren) {
  let oldStartIndex = 0
  let oldEndIndex = oldChildren.length - 1
  let oldStartVnode = oldChildren[0]
  let oldEndVnode = oldChildren[oldEndIndex]

  let newStartIndex = 0
  let newEndIndex = newChildren.length - 1
  let newStartVnode = newChildren[0]
  let newEndVnode = newChildren[newEndIndex]

  // 乱序比对需要用一个map来记录
  let map = makeMapByKey(oldChildren)
  function makeMapByKey(children) {
    let map = {}
    children.forEach((child, index) => {
      map[child.key] = index
    })
    return map
  }

  while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
    if (!oldStartVnode) {
      oldStartVnode = oldChildren[++oldStartIndex]
    } else if (!oldEndVnode) {
      oldEndVnode = oldChildren[--oldEndIndex]
    } else if (isSameVnode(oldStartVnode, newStartVnode)) {
      // 特殊情况，从头开始比对 abcd <===> abcdef
      patchVnode(oldStartVnode, newStartVnode)
      oldStartVnode = oldChildren[++oldStartIndex]
      newStartVnode = newChildren[++newStartIndex]
    } else if (isSameVnode(oldEndVnode, newEndVnode)) {
      // 特殊情况，从尾开始比对 eabcd <===> abcd
      patchVnode(oldEndVnode, newEndVnode)
      oldEndVnode = oldChildren[--oldEndIndex]
      newEndVnode = newChildren[++newEndIndex]
    } else if (isSameVnode(oldEndVnode, newStartVnode)) {
      // 特殊情况，交叉头尾比对 abcde <===> eabcd
      patchVnode(oldEndVnode, newStartVnode)
      // insertBefore 具有移动性，会将原来的元素移动走
      el.insertBefore(oldEndVnode.el, oldStartVnode.el)
      oldEndVnode = oldChildren[--oldEndIndex]
      newStartVnode = newChildren[++newStartIndex]
    } else if (isSameVnode(oldStartVnode, newEndVnode)) {
      // 特殊情况，交叉头尾比对 eabcd <===> abcde
      patchVnode(oldStartVnode, newEndVnode)
      // insertBefore 具有移动性，会将原来的元素移动走
      // insertBefore 如果是null，等同于appendChild
      el.insertBefore(oldStartVnode.el, oldEndVnode.el.nextSibling)
      oldStartVnode = oldChildren[++oldStartIndex]
      newEndVnode = newChildren[--newEndIndex]
    } else {
      // 乱序比对 adbe <===> qaebopd
      let moveIndex = map[newStartVnode.key]
      if (moveIndex) {
        // 如果有，表示可以复用
        let moveVnode = oldChildren[moveIndex]
        el.insertBefore(moveVnode.el, oldStartVnode.el)
        oldChildren[moveIndex] = undefined // 表示这个节点已经被移走
        patchVnode(moveVnode, newStartVnode)
      } else {
        // 创建新的并插入
        let newEle = createElm(newStartVnode)
        el.insertBefore(newEle, oldStartVnode.el)
      }
      newStartVnode = newChildren[++newStartIndex]
    }
  }

  // 新元素多了，要插入
  if (newStartIndex <= newEndIndex) {
    for (let i = newStartIndex; i <= newEndIndex; i++) {
      let childEl = createElm(newChildren[i])
      // 可能是向后追加的，也可能是向前追加的
      let anchor = newChildren[newEndIndex + 1] ? newChildren[newEndIndex + 1].el : null
      // insertBefore 如果是null，等同于appendChild
      el.insertBefore(childEl, anchor)
    }
  }

  // 老的多了，删除老的
  if (oldStartIndex <= oldEndIndex) {
    for (let i = oldStartIndex; i < oldEndIndex; i++) {
      // 有可能为 undefined，被移走了
      if (oldChildren[i]) {
        let childEl = oldChildren[i].el
        el.removeChild(childEl)
      }
    }
  }
}
